package com.jeasonzhao.commons.xml;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.dom4j.tree.DefaultCDATA;
import com.jeasonzhao.commons.utils.Algorithms;

public class Dom4JXmlBridge implements IXMLBridge
{
    private final static String DEFAULTENCODING = "UTF-8";
    public Dom4JXmlBridge()
    {
        super();
    }

    public void write(XMLNode node
                      ,java.io.OutputStream stream
                      ,String strEncoding
                      ,boolean showWholeXMLDocuments
                      ,boolean isCompatMode
        )
        throws XMLException
    {
        if(null == node)
        {
            return;
        }
        try
        {
            Document docu = reverseNode(node);
            OutputFormat format = isCompatMode ? OutputFormat.createCompactFormat() :
                OutputFormat.createPrettyPrint();
            if(null == strEncoding || strEncoding.trim().length() < 1)
            {
                strEncoding = DEFAULTENCODING;
            }
            format.setEncoding(strEncoding);
            java.io.Writer sw = new java.io.OutputStreamWriter(stream);
            XMLWriter writer = new XMLWriter(sw,format);
            writer.write(showWholeXMLDocuments ? docu : docu.getRootElement());
            sw.close();
        }
        catch(IOException ex)
        {
            throw new XMLException(ex.getMessage(),ex);
        }
    }

    public XMLNode read(String strXML)
        throws XMLException
    {
        return read(strXML,null);
    }

    public XMLNode read(String strXML,String strEncoding)
        throws XMLException
    {
        if(null == strXML || strXML.trim().length() < 1)
        {
            return null;
        }
        if(null == strEncoding || strEncoding.trim().length() < 1)
        {
            strEncoding = DEFAULTENCODING;
        }
        SAXReader reader = new SAXReader();
        try
        {
            java.io.InputStream s = new java.io.ByteArrayInputStream(strXML.getBytes(strEncoding));
            return buildNodes(reader.read(s));
        }
        catch(DocumentException ex)
        {
            throw new XMLException(ex.getMessage(),ex);
        }
        catch(XMLException ex)
        {
            throw ex;
        }
        catch(UnsupportedEncodingException ex)
        {
            throw new XMLException(ex.getMessage(),ex);
        }
    }

    public XMLNode read(java.io.InputStream stream)
        throws XMLException
    {
        SAXReader reader = new SAXReader();
        try
        {
            return buildNodes(reader.read(stream));
        }
        catch(DocumentException ex)
        {
            throw new XMLException(ex.getMessage(),ex);
        }
        catch(XMLException ex)
        {
            throw new XMLException(ex.getMessage(),ex);
        }
    }

    @SuppressWarnings("unchecked")
    private static XMLNode buildNodes(Element el)
        throws XMLException
    {
        XMLNode node = new XMLNode(el.getName());
        node.setInnerText(el.getText());
        for(Iterator<Attribute> ita = el.attributeIterator();ita.hasNext();)
        {
            Attribute a = ita.next();
            if(Algorithms.isEmpty(a.getValue()) == false)
            {
                node.addAttribute(a.getName(),a.getValue());
            }
        }
        for(Iterator<Element> it = el.elementIterator();it.hasNext();)
        {
            Element els = it.next();
            node.addNode(buildNodes(els));
        }
        return node;
    }

    private static Document reverseNode(XMLNode node)
    {
        if(null == node)
        {
            return null;
        }
        Document document = DocumentHelper.createDocument();
        buildReverseNode(document.addElement(node.getName()),node);
        return document;
    }

    private static void buildReverseNode(Element ele,XMLNode node)
    {
        if(null == ele || null == node)
        {
            return;
        }
        if(null != node.getInnerText())
        {
            ele.setText(node.getInnerText());
        }
        if(null != node.getCData())
        {
            ele.add(new DefaultCDATA(node.getCData()));
        }
        for(XMLAttribute attr : node.selectAttributes())
        {
            ele.addAttribute(attr.getName(),attr.getValue());
        }
        for(XMLNode sNode : node.selectNodes())
        {
            buildReverseNode(ele.addElement(sNode.getName()),sNode);
        }
    }

    private static XMLNode buildNodes(Document doc)
        throws XMLException
    {
        return buildNodes(doc.getRootElement());
    }
}
